import ij.ImagePlus;
import ij.plugin.PlugIn;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;


import java.awt.Color;


import java.awt.Font;

import java.awt.Polygon;
import java.io.PrintStream;

import java.util.Locale;
import java.util.Random; 



import pluginUtils.Circle;
import pluginUtils.Complexe; 
import pluginUtils.MobiusTransform;


public class IndraPearls2 extends ImagePlus implements PlugIn{
	{Locale.setDefault(Locale.US); }
	
	private ImageProcessor primeIP; 
	private int X=2000,Y=2000; // image size
	private int nbDrawn=0;
	private int level=6;
	private int order; 
	private static PrintStream output;
	private Complexe oldPoint=null,newPoint; 
	
	MobiusTransform unSurZ=new MobiusTransform(Complexe.createComplexeFromRI(1,0),Complexe.createComplexeFromRI(1,0),
			                                   Complexe.createComplexeFromRI(0,1),Complexe.createComplexeFromRI(0,-1)); 
	MobiusTransform unSurZInverse=unSurZ.inverse(); 
	
	
	private Random generator=new Random(); // One always needs some randomness
	
	private static String palette[]={"Red","Blue","Yellow","Green"};
	private static Color paleCol[]={Color.RED,Color.BLUE,Color.YELLOW,Color.GREEN,Color.BLACK,Color.CYAN,Color.DARK_GRAY,
		                             Color.MAGENTA,Color.ORANGE};
	
	// For the scaling of the image
	private double x0=0,x1=1,y0=0,y1=1;
	private double acoef,ccoef,vcoef,wcoef; 
	
	
	
	public void setCoefs(){
		acoef=X/(x1-x0); 
		vcoef=Y/(y1-y0); 
		ccoef=-acoef*x0; 
		wcoef=-vcoef*y0; 
	}
	 
	
	
	public void setBounds(double xl,double xu,double yl,double yu){
		x0=xl; 
		x1=xu;
		y0=yl;
		y1=yu; 
	}
	
	
	private static double altitude=0; 
	
	
	double x=Math.sqrt(2); 
	double u=Math.sqrt(2); 
	double k=1;
	double y=1; 
	double v=1; 
	
	/* a and A mappings */
	Complexe a=Complexe.createComplexeFromRI(u,0);
	Complexe b=Complexe.createComplexeFromRI(0,k*v); 
	Complexe c=Complexe.createComplexeFromRI(0,-v/k);
	Complexe d=Complexe.createComplexeFromRI(u,0); 
	MobiusTransform Transa=new MobiusTransform(a,b,c,d); 
	MobiusTransform TransA=Transa.inverse();
	
	/* b and B mappings */ 
	Complexe a1=Complexe.createComplexeFromRI(x,0);
	Complexe b1=Complexe.createComplexeFromRI(y,0); 
	Complexe c1=Complexe.createComplexeFromRI(y,0);
	Complexe d1=Complexe.createComplexeFromRI(x,0); 
	MobiusTransform Transb=new MobiusTransform(a1,b1,c1,d1);
	MobiusTransform TransB=Transb.inverse();
	
	/*****************************************************************************************/
	MobiusTransform[] ordered={Transa,TransB,TransA,Transb};
	MobiusTransform[] conjugates={unSurZ.mul(Transa.mul(unSurZ.inverse())),
			unSurZ.mul(TransB.mul(unSurZ.inverse())),
			unSurZ.mul(TransA.mul(unSurZ.inverse())),
			unSurZ.mul(Transb.mul(unSurZ.inverse()))
			};
	
	String letter[]={"a","B","A","b"};
	Circle[] circ={new Circle(Complexe.createComplexeFromRI(0,k*u/v),k/v),
			            new Circle(Complexe.createComplexeFromRI(-x/y,0),1/y),
			            new Circle(Complexe.createComplexeFromRI(0,-k*u/v),k/v),
			            new Circle(Complexe.createComplexeFromRI(x/y,0),1/y)};
	
	
	private void drawCircleInverse(Circle c,int level){
		nbDrawn++;
		Circle cp=unSurZ.circleImage(c); 
		double xCenter=cp.getCenter().getReal(); 
		double yCenter=cp.getCenter().getIm(); 
		
		
		int intX=(int)(xCenter*acoef+ccoef);
		int intY=(int)(yCenter*vcoef+wcoef);
		int intRx=(int)(cp.getRadius()*acoef);
		int intRy=(int)(cp.getRadius()*vcoef);
		
		primeIP.setColor(paleCol[level%4]);
		primeIP.drawOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
		String s="sphere{\n<"+xCenter+","+yCenter+","+altitude+">,";
		s+=cp.getRadius(); 
		s+=" texture{pigment {color "+palette[order]+" } finish {reflection 0.5 specular 0.7}}}"; 
		output.println(s); 
		altitude*=0.5;
		
	}
	
	
	
	private void drawCircle(Circle c,int level){
		nbDrawn++;
		double xCenter=c.getCenter().getReal(); 
		double yCenter=c.getCenter().getIm(); 
		
		
		int intX=(int)(xCenter*acoef+ccoef);
		int intY=(int)(yCenter*vcoef+wcoef);
		int intRx=(int)(c.getRadius()*acoef);
		int intRy=(int)(c.getRadius()*vcoef);
		
		primeIP.setColor(paleCol[level%paleCol.length]);
		//primeIP.fillOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
		primeIP.drawOval(intX-intRx,intY-intRy,intRx*2,intRy*2);
		String s="sphere{\n<"+xCenter+","+yCenter+","+altitude+">,";
		s+=c.getRadius(); 
		s+=" texture{pigment {color "+palette[order]+" } finish {reflection 0.5 specular 0.7}}}"; 
		output.println(s); 
		altitude*=0.5;
		
	}
	
	private void drawPoint(Complexe c){
		if(c.getModule()>3) return; 
		double xCenter=c.getReal(); 
		double yCenter=c.getIm(); 
		int intX=(int)(xCenter*acoef+ccoef);
		int intY=(int)(yCenter*vcoef+wcoef);
		primeIP.fillOval(intX-10,intY-10,20,20);
	}
	
	
	
	
	
	private void drawLine(double xd,double yd,double xf,double yf){
		int XD=(int)(xd*acoef+ccoef); 
		int YD=(int)(yd*vcoef+wcoef); 
		int XF=(int)(xf*acoef+ccoef); 
		int YF=(int)(yf*vcoef+wcoef);
		primeIP.drawLine(XD,YD,XF,YF); 
	}
	
	private static int index=0;
	
	private void drawLine(Complexe origin,Complexe destination){
		if(Complexe.distance(origin,destination)<0.5)
		if(index<100)
		drawLine(origin.getReal(),origin.getIm(),destination.getReal(),destination.getIm()); 
		index++;
	}
	
	// O -> transa
	// 1 -> transA
	// 2 -> transb
	// 3 -> transB
	private void buildSuccessors(Circle c,int level,int exclude){
		if(level==0) drawCircle(c,level); 
		if(level==0)  return;
		if(exclude!=0) buildSuccessors(conjugates[2].circleImage(c),level-1,1);  // A
		if(exclude!=1) buildSuccessors(conjugates[0].circleImage(c),level-1,0);  // a
		if(exclude!=2) buildSuccessors(conjugates[1].circleImage(c),level-1,3);  //B
		if(exclude!=3) buildSuccessors(conjugates[3].circleImage(c),level-1,2);  // b
	}
	
	
	private void buildSuccessors2(Complexe c,int level,int exclude){
		//if(level==0)  drawPoint(c);
		if(oldPoint!=null){
			double dx=c.getReal()-oldPoint.getReal(); 
			double dy=c.getIm()-oldPoint.getIm();
			if(level==0){
				drawLine(oldPoint,c); 
				oldPoint=new Complexe(c); 
			}
			
		} 
		oldPoint=new Complexe(c); 
		if(level==0){
			if(oldPoint!=null) drawLine(oldPoint,c); 
			oldPoint=new Complexe(c); 
		}
		if(level==-1000)  return;
		if(exclude==0) buildSuccessors2(TransB.pointImage(c),level-1,3); 
		if(exclude==1) buildSuccessors2(Transb.pointImage(c),level-1,2); 
		if(exclude==2) buildSuccessors2(Transa.pointImage(c),level-1,0); 
		if(exclude==3) buildSuccessors2(TransA.pointImage(c),level-1,1); 
	}
	
	private void buildSuccessors3(Complexe c,int level,int exclude){
		if(level==0) drawPoint(c); 
		if(level==0)  return;
		int choice=(1+exclude+generator.nextInt(3))%4; 
		if(choice==0) {buildSuccessors3(Transa.pointImage(c),level-1,choice);return;} 
		if(choice==1) {buildSuccessors3(TransA.pointImage(c),level-1,choice);return;} 
		if(choice==2) {buildSuccessors3(TransB.pointImage(c),level-1,choice);return;} 
		if(choice==3) {buildSuccessors3(Transb.pointImage(c),level-1,choice);return;} 
	}
	
	private void ExploreTree(Complexe c,int genIndex,int level,String s){
		//System.out.println(s);
		for(int k=genIndex-1;k<=genIndex+1;k++){
			int i=k; 
			if(k==-1)i=3; 
			if(i==4) i=0; 
			if(level==0){
				if(oldPoint!=null)
					drawLine(oldPoint,c); 
				oldPoint=new Complexe(c);
			}
			else {
				
				ExploreTree(ordered[i].pointImage(c),i,level-1,s+letter[i]);
			}
		}
		
	}
	
	
	
	public void run(String arg) {
		try{
			output=new PrintStream("IP2.txt");
		primeIP = new ColorProcessor(X,Y);
		
		
		setBounds(-30,30,-30,30); 
		setCoefs(); 
		
		Polygon background=new Polygon(new int[]{0,0,X,Y},new int[]{0,X,Y,0},4);
		primeIP.setColor(Color.WHITE);
		primeIP.fillPolygon(background);
		primeIP.setFont(new Font(null,Font.ITALIC,60));
		primeIP.setColor(Color.BLACK);
		primeIP.setLineWidth(5); 
		
		
		
		drawLine(x0,0,x1,0);
		drawLine(0,y0,0,y1);
		
		System.out.println("a:\n"+Transa); 
		System.out.println("********************");
		System.out.println("A:\n"+TransA); 
		System.out.println("********************");
		System.out.println("b:\n"+Transb); 
		System.out.println("********************");
		System.out.println("B:\n"+TransB); 
		System.out.println("********************");
		
		MobiusTransform prod=Transa.mul(Transb);
		prod=prod.mul(TransA); 
		prod=prod.mul(TransB); 
		System.out.println(prod);
		
		MobiusTransform prod2=TransA.mul(TransB); 
		prod2=Transb.mul(prod2); 
		prod2=Transa.mul(prod2);
		//System.out.println(prod2);
		
		System.out.println("test "); 
		System.out.println(unSurZ.mul(Transb.mul(unSurZ.inverse()))); 
		
		System.out.println("conjugor : n"+unSurZ); 
		System.out.println("inverse conjugor : n"+unSurZ.inverse()); 
		
		System.out.println(unSurZ);
		System.out.println(unSurZInverse); 
		
		
		
		
	
		setProcessor("Verifying", primeIP);
		/***************** Drawing instructions come here *****************************************/
		
		for(int k=0;k<4;k++) {
			primeIP.setColor(Color.BLACK);
			drawPoint(ordered[k].getFixPoint0());
			primeIP.setColor(paleCol[k]);
			ExploreTree(ordered[(k+2)%4].getFixPoint0(),k,10,letter[k]); 
		}
		
		
		/*
		//Cb
		Complexe center=Complexe.createComplexeFromRI(x/y,0); 
		IndraCircle seed=new IndraCircle(center,1/y);
		IndraCircle seed0=unSurZ.circleImage(seed); 
		//drawCircle(seed);
		primeIP.setColor(Color.RED);
		order=0; 
		buildSuccessors(seed0, level, 2);
		
		//CB
		center=Complexe.createComplexeFromRI(-x/y, 0);
		seed=new IndraCircle(center,1/y);
		seed0=unSurZ.circleImage(seed); 
		//drawCircle(seed);
		primeIP.setColor(Color.BLUE);
		order=1;
		buildSuccessors(seed0, level, 3);
		
		//Ca
		center=Complexe.createComplexeFromRI(0,k*u/v); 
		seed=new IndraCircle(center,k/v);  
		seed0=unSurZ.circleImage(seed); 
		//drawCircle(seed);
		primeIP.setColor(Color.GREEN);
		order=2; 
		buildSuccessors(seed0, level, 0);
		
		//CA
		center=Complexe.createComplexeFromRI(0,-k*u/v); 
		seed=new IndraCircle(center,k/v);  
		seed0=unSurZ.circleImage(seed); 
		//drawCircle(seed);
		primeIP.setColor(Color.YELLOW);
		order=3; 
		buildSuccessors(seed0, level, 1);
		
		*/
		/***************** End of drawing instructions ********************************************/
		show();
		System.out.println("//done "+nbDrawn); 
		}
		catch(Exception e){System.out.println(e.getMessage()+"plante"); System.exit(0);}
	}	
}
	